package Model;


import Main.Main;
import Settings.Settings;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.*;
import java.lang.reflect.Array;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;

public class ImageModel {
    private ImageModelListener myListener;
    private BufferedImage myImage;
    private FilteredImageSource filteredImageSource;
    private String myFileName;
    private File myFile;
    private String myFormat;
    private ArrayList<BufferedImage> imageHistory;
    private ConvolveOp convolveOp;
    private Kernel kernel;
    private int currentImageIndex;
    public static final String IMAGE_NOT_OPENED = "img_not_opened";
    public static final String IMAGE_NOT_SAVED = "img_not_saved";
    public static final String IMAGE_CHANGED = "img_change";

 private String diffPath = null;

 
 
  private String goldenImagePath = null;

    
  


    private ArrayList<LinearFilter> filterLibrary;

    public ArrayList<LinearFilter> getFilterLibrary() {
        return filterLibrary;
    }

    public ImageModel() {
        imageHistory = new ArrayList<BufferedImage>();
        currentImageIndex = -1;
        //set integrate filter (toDel)
        float[] kData = new float[9];
        for (int i = 0; i < kData.length; ++i)
            kData[i] = 1.0f / 9.0f;
        kernel = new Kernel(3, 3, kData);
        convolveOp = new ConvolveOp(kernel);
        loadFilterLibrary();
        goldenImagePath = Settings.getInstance().get(Settings.GOLDEN_IMAGE_FILENAME);
        if(goldenImagePath == null){
            goldenImagePath = "." + File.separator + "golden";//System.getProperty("user.home");
            Settings.getInstance().set(Settings.GOLDEN_IMAGE_FILENAME,"./golden");
        }
        File golden = new File(goldenImagePath);
        if (!(golden.exists())){
            golden.mkdir();
        }
        diffPath = Settings.getInstance().get(Settings.DIFF_FILENAME);
        if(diffPath == null){
            diffPath = "." + File.separator + "diff";//System.getProperty("user.home");
            Settings.getInstance().set(Settings.DIFF_FILENAME,"./diff");
        }
        File diff = new File(diffPath);
        if (!(diff.exists())){
            diff.mkdir();
        }

    }

    public void setKernel(int size, float[] data, float div) {
        float[] kData = new float[data.length];
        for (int i = 0; i < kData.length; ++i)
            kData[i] = data[i] / div;
        kernel = new Kernel(size, size, kData);
        convolveOp = new ConvolveOp(kernel);
    }

    private void loadFilterLibrary() {
        try {
            //URL fileURL = Main.class.getResource("/resources/filters.xml");
            XMLDecoder decoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("filters.xml")));
            filterLibrary = (ArrayList<LinearFilter>)decoder.readObject();
            decoder.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("Filters couln't be opened");
            filterLibrary = new ArrayList<LinearFilter>();
        }

    }

    private void saveFilterLibrary() {
        try {
            //URL fileURL = Main.class.getResource("/resources/filters.xml");
            XMLEncoder encoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream("filters.xml")));
            encoder.writeObject(filterLibrary);
            encoder.close();
        } catch (IOException e) {
            e.printStackTrace();
            System.err.println("filters couldn't be saved");
        }
    }

    private void savePaths(){
        Settings.getInstance().set(Settings.GOLDEN_IMAGE_FILENAME, goldenImagePath);
        Settings.getInstance().set(Settings.DIFF_FILENAME, diffPath);
    }

    public void saveSettings(){
        saveFilterLibrary();
        savePaths();
    }
    
    public LinearFilter getFilterByName(String name) {
        LinearFilter filter = null;
        for (int i = 0; i < filterLibrary.size(); ++i)
            if (filterLibrary.get(i).getName().equals(name)) {
                filter = filterLibrary.get(i);
                break;
            }
        return filter;
    }
        

    /*
    Open source code
     */
    private BufferedImage deepCopy(BufferedImage bi) {
        ColorModel cm = bi.getColorModel();
        boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
        WritableRaster raster = bi.copyData(null);
        return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
    }

    private void dropHistory() {
        currentImageIndex = -1;
        imageHistory.clear();
    }

    private void updateHistory() {
        currentImageIndex++;
        imageHistory.add(currentImageIndex, deepCopy(myImage));
        for (int i = currentImageIndex + 1; i < imageHistory.size(); i++) {
            imageHistory.remove(i);
        }
    }

    public boolean isCurrentIndexFirst() {
        if (imageHistory.isEmpty() || currentImageIndex < 0)
            return false;
        return (currentImageIndex == 0);
    }

    public boolean isCurrentIndexLast() {
        if (imageHistory.isEmpty() || currentImageIndex < 0)
            return false;
        return (currentImageIndex == imageHistory.size() - 1);
    }

    public boolean undo() {
        if (currentImageIndex > 0 && !imageHistory.isEmpty()) {
            currentImageIndex--;
            myImage = deepCopy(imageHistory.get(currentImageIndex));
            fireImageChanged();
            return true;
        }
        return false;
    }

    public boolean redo() {
        if (currentImageIndex >= 0 && !imageHistory.isEmpty() && currentImageIndex < imageHistory.size() - 1) {
            currentImageIndex++;
            myImage = deepCopy(imageHistory.get(currentImageIndex));
            fireImageChanged();
            return true;
        }
        return false;
    }

    public void setListener(ImageModelListener listener) {
        myListener = listener;
    }

    public BufferedImage getImage() {
        return myImage;
    }

    private void fireImageChanged() {
        try {
            myListener.imageChanged(new ImageModelEvent(this, IMAGE_CHANGED, null));
        } catch (NullPointerException e) {
            System.err.println("Model.ImageModel listener is null");
        }
    }



    private void fireImageNotOpened(String fileName) {
        try {
            myListener.imageChanged(new ImageModelEvent(this, IMAGE_NOT_OPENED, fileName));
        } catch (NullPointerException e) {
            System.err.println("Model.ImageModel listener is null");
        }
    }

    private void fireImageNotSaved(String fileName) {
        try {
            myListener.imageChanged(new ImageModelEvent(this, IMAGE_NOT_SAVED, fileName));
        } catch (NullPointerException e) {
            System.err.println("Model.ImageModel listener is null");
        }
    }

    public void save(String fileName, String formatName) {
        save(new File(fileName), formatName);
    }

    public void save(File file, String formatName) {
        if (myImage == null) {
            fireImageNotSaved(file.getName());
        }
        try {
            if (!ImageIO.write(myImage, formatName, file)) {
                fireImageNotSaved(file.getName());
            } else {
                myFileName = file.getName();
                myFormat = formatName;
                myFile = file;
            }
        } catch (IOException e) {
            fireImageNotSaved(file.getName());
            return;
        }
        fireImageChanged();
    }

    public void open(String fileName) {
        open(new File(fileName));
    }

    public void erosion(int r) {
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);
        int pos = 0;
        for (int y = 0; y != height; ++y) {
            for (int x = 0; x != width; ++x) {
                //int posMin =  pos;
                //int min = pixels[pos];
                int temp = pixels[pos];
                int minR = (temp & 0xff0000) >> 16;
                int minG = (temp & 0xff00) >> 8;
                int minB = (temp & 0xff);
                //min = ((min & 0xff) + ((min & 0xff00) >> 8) + ((min & 0xff0000) >> 16)) / 3;// + pixels[pos + 1] + pixels[pos + 2]) / 3;

                for (int i = Math.max(0, x - r); i <= Math.min(width - 1, x + r); ++i) {
                    for (int j = Math.max(0, y - r); j <= Math.min(height - 1, y + r); ++j) {
                        if (i != x || j != y) {
                            int idx = j * width + i;
                            temp = pixels[idx];
                            int tempR = (temp & 0xff0000) >> 16;
                            int tempG = (temp & 0xff00) >> 8;
                            int tempB = (temp & 0xff);
                            //temp = ((temp & 0xff) + ((temp & 0xff00) >> 8) + ((temp & 0xff0000) >> 16)) / 3;
                            //if (temp < min) {
                            //    min = temp;
                            //    posMin = idx;
                            //}
                            if (tempR < minR)
                                minR = tempR;
                            if (tempG < minG)
                                minG = tempG;
                            if (tempB < minB)
                                minB = tempB;

                        }
                    }
                }
                newPixels[pos] = (minR << 16) + (minG << 8) + minB;//pixels[posMin];
                pos++;
            }
        }

        myImage.setRGB(0, 0, width, height, newPixels, 0, width);

        updateHistory();
        fireImageChanged();
    }

    public void dilatation(int r) {
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);

        int pos = 0;
        for (int y = 0; y != height; ++y) {
            for (int x = 0; x != width; ++x) {
                //int posMax =  pos;
                int temp = pixels[pos];
                int maxR = (temp & 0xff0000) >> 16;
                int maxG = (temp & 0xff00) >> 8;
                int maxB = (temp & 0xff);
                //max = ((max & 0xff) + ((max & 0xff00) >> 8) + ((max & 0xff0000) >> 16)) / 3;// + pixels[pos + 1] + pixels[pos + 2]) / 3;

                for (int i = Math.max(0, x - r); i <= Math.min(width - 1, x + r); ++i) {
                    for (int j = Math.max(0, y - r); j <= Math.min(height - 1, y + r); ++j) {
                        if (i != x || j != y) {
                            //int idx = j * width + i;
                            //int temp = pixels[idx];
                            //temp = ((temp & 0xff) + ((temp & 0xff00) >> 8) + ((temp & 0xff0000) >> 16)) / 3;
                            //if (temp > max) {
                            //    max = temp;
                            //    posMax = idx;
                            //}
                            int idx = j * width + i;
                            temp = pixels[idx];
                            int tempR = (temp & 0xff0000) >> 16;
                            int tempG = (temp & 0xff00) >> 8;
                            int tempB = (temp & 0xff);
                            if (tempR > maxR)
                                maxR = tempR;
                            if (tempG > maxG)
                                maxG = tempG;
                            if (tempB > maxB)
                                maxB = tempB;
                        }
                    }
                }
                newPixels[pos] = (maxR << 16) + (maxG << 8) + maxB;//pixels[posMax];
                pos++;
            }
        }

        myImage.setRGB(0, 0, width, height, newPixels, 0, width);
        updateHistory();
        fireImageChanged();
    }

    public void medianFilter(int r) {
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);

        short[] windowR = new short[(2 * r + 1) * (2 * r + 1)];
        short[] windowG = new short[(2 * r + 1) * (2 * r + 1)];
        short[] windowB = new short[(2 * r + 1) * (2 * r + 1)];
        int pixWrite, temp;

        int pos = 0;
        for (int y = 0; y != height; ++y) {
            for (int x = 0; x != width; ++x) {

                pixWrite = 0;

                for (int i = Math.max(0, x - r); i <= Math.min(width - 1, x + r); ++i) {
                    for (int j = Math.max(0, y - r); j <= Math.min(height - 1, y + r); ++j) {
                        int idx = j * width + i;
                        temp = pixels[idx];
                        short tempR = (short)((temp & 0xff0000) >> 16);
                        short tempG = (short)((temp & 0xff00) >> 8);
                        short tempB = (short)(temp & 0xff);
                        windowR[pixWrite] = tempR;
                        windowG[pixWrite] = tempG;
                        windowB[pixWrite] = tempB;
                        pixWrite++;
                    }
                }
                Arrays.sort(windowR, 0, pixWrite);
                Arrays.sort(windowG, 0, pixWrite);
                Arrays.sort(windowB, 0, pixWrite);
                short resR = windowR[pixWrite / 2];
                short resG = windowG[pixWrite / 2];
                short resB = windowB[pixWrite / 2];
                newPixels[pos] = (resR << 16) + (resG << 8) + resB;//pixels[posMax];
                pos++;
            }
        }

        myImage.setRGB(0, 0, width, height, newPixels, 0, width);
        updateHistory();
        fireImageChanged();

    }


    public void inversion() {
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);

        for (int pos = 0; pos < pixels.length; ++pos) {
            newPixels[pos] = 0xffffff ^ pixels[pos];
        }

        myImage.setRGB(0, 0, width, height, newPixels, 0, width);
        updateHistory();
        fireImageChanged();
    }

    public void bw() {
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);

        for (int pos = 0; pos < pixels.length; ++pos) {
            int temp = pixels[pos];
            temp = (((temp & 0xff) + ((temp & 0xff00) >> 8) + ((temp & 0xff0000) >> 16)) / 3) & 0xff;
            newPixels[pos] = temp + (temp << 8) + (temp << 16);
        }

        myImage.setRGB(0, 0, width, height, newPixels, 0, width);
        updateHistory();
        fireImageChanged();
    }

    public void linearFilter() {
        //Raster oldRaster = myImage.getData();
        BufferedImage oldImage = deepCopy(myImage);
        convolveOp.filter(oldImage, myImage);
        updateHistory();
        fireImageChanged();
    }
    
    public void WhiteNoise(float p, int d) {
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);

        newPixels = Filter.WhiteNoise(0, 0, width, height, width, pixels, p,  d);

        myImage.setRGB(0, 0, width, height, newPixels, 0, width);

        updateHistory();
        fireImageChanged();
    }

    public void saltPepper(double p) {
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);

        newPixels = Filter.saltPepper(0, 0, width, height, width, pixels, p);

        myImage.setRGB(0, 0, width, height, newPixels, 0, width);

        updateHistory();
        fireImageChanged();
    }

    public void Dust(float p, int d) {
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);

        newPixels = Filter.Dust(0, 0, width, height, width, pixels, p,  d);

        myImage.setRGB(0, 0, width, height, newPixels, 0, width);

        updateHistory();
        fireImageChanged();
    }

    private float logFunction(float x, float y, float s)
    {
        float pi = (float)Math.PI;
        return (float) (-1.0f / (pi * s*s*s*s) * (1.0f - (x*x + y*y) / (2.0f*s*s)) * Math.exp(-(x*x + y*y) / (2.0f*s*s)));
    }



    public void LoG(int r, float s)
    {
        float xStart = -r;
        float yStart = -r;
        float x, y = yStart;
        int size = 1 + 2 * r;
        float[] data = new float[size*size];
        float sum = 0.0f;
        for (int i= 0; i < size; i++) {
            x = xStart;
            for (int j = 0; j < size; j++) {
                data[i * size + j] = logFunction(x, y, s);
                x += 1;
                sum += data[i * size + j];
            }
            y += 1;
        }
        setKernel(size, data, sum);
        linearFilter();
    }
    
    public void motionBlur(float angle, float distance, int iterations) {
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);
        int pos = 0;
        float translateX = (float)(width * distance * Math.cos(angle));
        float translateY = (float)(height * distance * Math.sin(angle));
        for (int y = 0; y != height; ++y) {
            for (int x = 0; x != width; ++x) {
                //int pix = pixels[pos];
                int pixR = 0;//(pix & 0xff0000) >> 16;
                int pixG = 0;// (pix & 0xff00) >> 8;
                int pixB = 0;//(pix & 0xff);
                int count = 0;

                for (int i = 0; i != iterations; ++i) {
                    float mul = ((float)(i)) / iterations;
                    int newX = x + (int)(mul * translateX);
                    int newY = y + (int)(mul * translateY);
                    if (newX >= 0 && newX < width && newY >= 0 && newY < height)
                    {
                        int rgb = pixels[newY * width + newX];
                        pixR += (rgb & 0xff0000) >> 16;
                        pixG += (rgb & 0xff00) >> 8;
                        pixB += (rgb & 0xff);
                        count++;
                    }
                    else
                        break;
                }
                if (count > 0) {
                    pixR = (pixR / count) & 0xff;
                    pixB = (pixB / count) & 0xff;
                    pixG = (pixG / count) & 0xff;
                    newPixels[pos] = (pixR << 16) + (pixG << 8) + pixB;
                }
                else
                    newPixels[pos] = pixels[pos];

                pos++;
            }
        }

        myImage.setRGB(0, 0, width, height, newPixels, 0, width);

        updateHistory();
        fireImageChanged();        
    }


    private float gaussian(float x0, float x, float s)
    {
        float pi = (float)Math.PI;
        return (float) (1 / (Math.sqrt(2*pi) * s) * Math.exp(-(x0 - x)*(x0 - x) / (2 * s*s)));
        //  return (float) (10 * Math.exp(-(x0 - x)*(x0 - x) / (2 * s*s)));
    }
    
    private float[] createGaussianKernel5x5() {
        float[] kernel = new float[25];
        float mul = 1.0f / 273.0f;
        kernel[0] = 1.0f * mul;   kernel[1] = 4.0f * mul;   kernel[2] = 7.0f * mul;   kernel[3] = 4.0f * mul;   kernel[4] = 1.0f * mul;
        kernel[5] = 4.0f * mul;   kernel[6] = 16.0f * mul;  kernel[7] = 26.0f * mul;  kernel[8] = 16.0f * mul;  kernel[9] = 4.0f * mul;
        kernel[10] = 7.0f * mul;  kernel[11] = 26.0f * mul; kernel[12] = 41.0f * mul; kernel[13] = 26.0f * mul; kernel[14] = 7.0f * mul;
        kernel[15] = 4.0f * mul;  kernel[16] = 16.0f * mul; kernel[17] = 26.0f * mul; kernel[18] = 16.0f * mul; kernel[19] = 4.0f * mul;
        kernel[20] = 1.0f * mul;  kernel[21] = 4.0f * mul;  kernel[22] = 7.0f * mul;  kernel[23] = 4.0f * mul;  kernel[24] = 1.0f * mul;
        return kernel;
    }
    private float[] createGaussianKernel(int r) {
        int n = (2 * r + 1);
        float[] kernel = new float[n * n];
        float sum = 0.0f;
        float sigma = r / 3.0f;
        //float normMul = 1.0f / ((float)Math.sqrt(2.0 * Math.PI) * sigma);
        for (int i = -r; i <= r; ++i)
            for (int j = -r; j <= r; ++j) {
                kernel[(i + r) * n + j + r] = (float)Math.exp(-(i * i + j * j) / (2.0 * sigma * sigma));
                sum += kernel[(i + r) * n + j + r];
            }
        //for (int i = 0; i < n * n; ++i)
        //    kernel[i] /= sum;
        return kernel;
    }
    
    private int cNorm1(int r1, int g1, int b1, int r2, int g2, int b2) {
        return Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2);        
    }

    private int cNorm2(int r1, int g1, int b1, int r2, int g2, int b2) {
        return (int)Math.sqrt(((float)(r1 - r2)) * (r1 - r2) + ((float)(g1 - g2)) * (g1 - g2) + ((float)(b1 - b2)) * (b1 - b2));
    }

    private int cNormInf(int r1, int g1, int b1, int r2, int g2, int b2) {
        return Math.max(Math.max(Math.abs(r1 - r2), Math.abs(g1 - g2)), Math.abs(b1 - b2));
    }

    public void meanShift(int rd, int rc, int iterations, int maxLevel) {
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        //int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);
        int[][] gaussiangPyramid = new int[maxLevel + 1][];
        gaussiangPyramid[0] = new int[width * height];
        gaussiangPyramid[0] = pixels.clone();
        float[] gaussKernel = createGaussianKernel(rd);
        int length = 2 *rd + 1;
        int levelWidth = width / 2, levelHeight = height / 2;
        for (int l = 1; l <= maxLevel; ++l) {
            gaussiangPyramid[l] = new int[levelHeight * levelWidth];
            for (int y = 0; y < levelHeight; ++y)
                for (int x = 0; x < levelWidth; ++x) {
                    int r = 0, g = 0, b = 0;
                    float sum = 0.0f;
                    for (int i = - rd; i <= rd; ++i)
                        for (int j = -rd; j <= rd; ++j) {
                            if (x + i >= 0 && x + i < levelWidth && y + j >= 0 && y + j < levelHeight) {
                                int temp = gaussiangPyramid[l - 1][2 * (2 * y + j) * levelWidth + 2 * x + i];
                                r += (int)(gaussKernel[(j + rd) * length + i + rd] * ((temp >> 16) & 0xff));
                                g += (int)(gaussKernel[(j + rd) * length + i + rd] * ((temp >> 8) & 0xff));
                                b += (int)(gaussKernel[(j + rd) * length + i + rd] * ((temp) & 0xff));
                                sum += gaussKernel[(j + rd) * length + i + rd];
                            }
                        }
                    r = (int)(r / sum);
                    g = (int)(g / sum);
                    b = (int)(b / sum);
                    gaussiangPyramid[l][y * levelWidth + x] = (r << 16) + (g << 8) + b;
                }
            levelWidth /= 2;
            levelHeight /= 2;
        }
        System.out.println("Creating gaussian pyramid finished\n");
        // Поиск начального приближения
        for (int y = 0; y < height; ++y)
            for (int x = 0; x < width; ++x) {
                int curX = x, curY = y, it = 0;
                int curPix = pixels[curY * width + curX];
                int curR = (curPix >> 16) & 0xff;
                int curG = (curPix >> 8) & 0xff;
                int curB = curPix & 0xff;
                while (it < iterations) {
                    int count = 0, sumX = 0, sumY = 0, sumR = 0, sumG = 0, sumB = 0;
                    float sum = 0.0f;
                    for (int i = - rd; i <= rd; ++i)
                        for (int j = -rd; j <= rd; ++j) {
                            if (curX + i >= 0 && curX + i < width && curY + j >= 0 && curY + j < height) {
                                int temp = pixels[(curY + j) * width + (curX + i)];
                                int tempR = (temp >> 16) & 0xff;
                                int tempG = (temp >> 8) & 0xff;
                                int tempB = temp & 0xff;
                                if (cNorm2(curR, curG, curB, tempR, tempG, tempB) < rc) {
                                    sumX += curX + i;
                                    sumY += curY + j;
                                    sumR += (int)(gaussKernel[(j + rd) * length + i + rd] * tempR);
                                    sumG += (int)(gaussKernel[(j + rd) * length + i + rd] * tempG);
                                    sumB += (int)(gaussKernel[(j + rd) * length + i + rd] * tempB);
                                    sum += gaussKernel[(j + rd) * length + i + rd];
                                    count++;
                                }
                            }
                        }
                    if (count == 0)
                        break;
                    int newX = sumX / count;
                    int newY = sumY / count;
                    curR = (int)(sumR / sum);
                    curB = (int)(sumB / sum);
                    curG = (int)(sumG / sum);
                    if (newX == curX && newY == curY) {
                        break;
                    }
                    else {
                        curX = newX;
                        curY = newY;
                    }
                    it++;
                }
                gaussiangPyramid[0][y * width + x] = (curR << 16) + (curG << 8) + curB;
            }
        System.out.println("Finished mean shift algorithm for zero-level\n");
        levelWidth = width / 2;
        levelHeight = height / 2;
        // Уточнение областей
        for (int l = 1; l <= maxLevel; ++l) {
            for (int x = 0; x <levelWidth; ++x)
                for (int y = 0; y < levelHeight; ++y) {
                    int curX = x, curY = y, it = 0;
                    int curPix = gaussiangPyramid[l - 1][4 * curY * levelWidth + 2 * curX];
                    int curR = (curPix >> 16) & 0xff;
                    int curG = (curPix >> 8) & 0xff;
                    int curB = curPix & 0xff;    
                    int lowPix = gaussiangPyramid[l][curY * levelWidth + curX];
                    int lowR = (lowPix >> 16) & 0xff;
                    int lowG = (lowPix >> 8) & 0xff;
                    int lowB = lowPix & 0xff;
                    if (cNorm2(curR, curB, curG, lowR, lowB, lowG) >= rc) {

                        while (it < iterations) {
                            int count = 0, sumX = 0, sumY = 0, sumR = 0, sumG = 0, sumB = 0;
                            float sum = 0.0f;
                            for (int i = - rd; i <= rd; ++i)
                                for (int j = -rd; j <= rd; ++j) {
                                    if (curX + i >= 0 && curX + i < levelWidth && curY + j >= 0 && curY + j < levelHeight) {
                                        //int temp = gaussiangPyramid[0][((2 << (l - 1)) * curY + j) * width + (2 << (l - 1)) * curX + i];
                                        int temp = gaussiangPyramid[l - 1][2 * (2 * curY + j) * levelWidth + 2 * curX + i];
                                        int tempR = (temp >> 16) & 0xff;
                                        int tempG = (temp >> 8) & 0xff;
                                        int tempB = temp & 0xff;
                                        if (cNorm2(curR, curG, curB, tempR, tempG, tempB) < rc) {
                                            sumX += curX + i;
                                            sumY += curY + j;
                                            sumR += (int)(gaussKernel[(j + rd) * length + i + rd] * tempR);
                                            sumG += (int)(gaussKernel[(j + rd) * length + i + rd] * tempG);
                                            sumB += (int)(gaussKernel[(j + rd) * length + i + rd] * tempB);
                                            sum += gaussKernel[(j + rd) * length + i + rd];
                                            count++;
                                        }
                                    }
                                }
                            if (count == 0)
                                break;
                            int newX = sumX / count;
                            int newY = sumY / count;
                            curR = (int)(sumR / sum);
                            curB = (int)(sumB / sum);
                            curG = (int)(sumG / sum);
                            if (newX == curX && newY == curY) {
                                break;
                            }
                            else {
                                curX = newX;
                                curY = newY;
                            }
                            it++;
                        }
                        //gaussiangPyramid[l][y * levelWidth + x] = (curR << 16) + (curG << 8) + curB;

                    }
                    gaussiangPyramid[l][y * levelWidth + x] = (curR << 16) + (curG << 8) + curB;
                }
            for (int x = 0; x <levelWidth; ++x)
                for (int y = 0; y < levelHeight; ++y)
                    gaussiangPyramid[0][(2 << (l - 1)) * y * width + (2 << (l - 1)) * x] = gaussiangPyramid[l][y * levelWidth + x];
            System.out.printf("Finished mean shift algorithm for %d-level\n", l);
            levelWidth /= 2;
            levelHeight /= 2;
        }  



        myImage.setRGB(0, 0, width, height, gaussiangPyramid[0], 0, width);

        updateHistory();
        fireImageChanged();
    }

    public void zoomBlur(float scale, int iterations) {
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);
        int pos = 0;
        int cx = width / 2;
        int cy = height / 2;
        for (int y = 0; y != height; ++y) {
            for (int x = 0; x != width; ++x) {
                //int pix = pixels[pos];
                int pixR = 0;//(pix & 0xff0000) >> 16;
                int pixG = 0;// (pix & 0xff00) >> 8;
                int pixB = 0;//(pix & 0xff);
                int count = 0;

                for (int i = 0; i != iterations; ++i) {
                    float mul = ((float)(i)) / iterations;
                    int newX = (int)((x - cx)  * (mul * scale) + cx);
                    int newY = (int)((y - cy) * (mul * scale) + cy);
                    if (newX >= 0 && newX < width && newY >= 0 && newY < height)
                    {
                        int rgb = pixels[newY * width + newX];
                        pixR += (rgb & 0xff0000) >> 16;
                        pixG += (rgb & 0xff00) >> 8;
                        pixB += (rgb & 0xff);
                        count++;
                    }
                    else
                        break;
                }
                if (count > 0) {
                    pixR = (pixR / count) & 0xff;
                    pixB = (pixB / count) & 0xff;
                    pixG = (pixG / count) & 0xff;
                    newPixels[pos] = (pixR << 16) + (pixG << 8) + pixB;
                }
                else
                    newPixels[pos] = pixels[pos];

                pos++;
            }
        }

        myImage.setRGB(0, 0, width, height, newPixels, 0, width);

        updateHistory();
        fireImageChanged();
    }

    public void spinBlur(float alpha, int iterations) {
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);
        int pos = 0;
        int cx = width / 2;
        int cy = height / 2;
        for (int y = 0; y != height; ++y) {
            for (int x = 0; x != width; ++x) {
                //int pix = pixels[pos];
                int pixR = 0;//(pix & 0xff0000) >> 16;
                int pixG = 0;// (pix & 0xff00) >> 8;
                int pixB = 0;//(pix & 0xff);
                int count = 0;

                for (int i = 0; i != iterations; ++i) {
                    float mul = ((float)(i)) / iterations;
                    int newX = cx + (int)((x - cx)  * Math.cos(mul * alpha) - (y - cy) * Math.sin(mul * alpha));
                    int newY = cy + (int)((x - cx)  * Math.sin(mul * alpha) + (y - cy) * Math.cos(mul * alpha));
                    if (newX >= 0 && newX < width && newY >= 0 && newY < height)
                    {
                        int rgb = pixels[newY * width + newX];
                        pixR += (rgb & 0xff0000) >> 16;
                        pixG += (rgb & 0xff00) >> 8;
                        pixB += (rgb & 0xff);
                        count++;
                    }
                    else
                        break;
                }
                if (count > 0) {
                    pixR = (pixR / count) & 0xff;
                    pixB = (pixB / count) & 0xff;
                    pixG = (pixG / count) & 0xff;
                    newPixels[pos] = (pixR << 16) + (pixG << 8) + pixB;
                }
                else
                    newPixels[pos] = pixels[pos];

                pos++;
            }
        }

        myImage.setRGB(0, 0, width, height, newPixels, 0, width);

        updateHistory();
        fireImageChanged();
    }



    private class ColorClaster {
        private int color;
        private int x;
        private int y;
        private ArrayList<Point> points;

        public float lambda;
        
        public ColorClaster(int color) {
            this.color = color;
            points = new ArrayList<Point>();
            x = y = 0;
            lambda = 0.0f;
        }

        public void addPoint(int x, int y) {
            points.add(new Point(x, y));
        }
        
        public int getDistance(int c, int _x, int _y) {
            //float res1 = 0;
            int res = 0;
            res += Math.abs((c & 0xff) - (color & 0xff));
            res += Math.abs(((c >> 8) &0xff) - ((color >> 8) & 0xff));
            res += Math.abs(((c >> 16) &0xff) - ((color >> 16) & 0xff));
            if (lambda > 0.0f) {
                float res2 = 0;
                res2 += ((float)(x- _x)) * (x - _x);
                res2 += ((float)(y- _y)) * (y - _y);
                res2 = (float)Math.sqrt(res2);
                res = (int)(lambda * res2 + (1.0f - lambda) * res);
            }
            return res;
        }

        public boolean recalculate(int[] pixels, int width) {
            double r = 0.0;
            double g = 0.0;
            double b = 0.0;
            double tempX = 0.0;
            double tempY = 0.0;
            for (int i = 0; i < points.size(); ++i)
            {
                int temp = pixels[points.get(i).y * width + points.get(i).x]; 
                r += (temp >> 16) & 0xff;
                g += (temp >> 8) & 0xff;
                b += temp & 0xff;
                tempX += points.get(i).x;
                tempY += points.get(i).y;
            }                   
            if (points.size() > 0)
            {
                r /= points.size();
                g /= points.size();
                b /= points.size();
                tempX /= points.size();
                tempY /= points.size();
                int newColor = ((int)r << 16) + ((int)g << 8) + (int)b;
                if (newColor == color)
                    return false;
                color = newColor;
                x = (int)tempX;
                y = (int)tempY;
                points.clear();
                return true;
            }
            return false;
        }
        
        public void setNewColors(int[] pixels, int width) {
            for (int i = 0; i < points.size(); ++i)
                pixels[points.get(i).y * width + points.get(i).x] = color;
        }     
                
        
        public int getColor(){
            return color;
        }
        

    }
    
    public void k_means(int numOfColors, int numOfIterations, float lambda) {
        ColorClaster[] clasters = new ColorClaster[numOfColors];
        for (int i = 0; i < numOfColors; ++i)
            clasters[i] = new ColorClaster((0xffffff / (numOfColors - 1)) * i);
        //System.out.println("init \n");
        //for (int k = 0; k < numOfColors; ++k)
        //    System.out.printf("%x  ", clasters[k].getColor());
        //System.out.println("\n");
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);
        int[] newPixels = new int[width * height];
        boolean finish = true;
        int iter = 0;
        if (lambda > 0.0f) {
            for (int j = 0; j < height; ++j)
                for (int i = 0; i < width; ++i) {
                    int idx = j * width + i;
                    int min = 0xffffff;
                    int minIdx = 0;
                    for (int k = 0; k < numOfColors; ++k)
                    {
                        int temp = clasters[k].getDistance(pixels[idx], i, j);
                        if (temp < min) {
                            min = temp;
                            minIdx = k;
                        }
                    }
                    clasters[minIdx].addPoint(i, j);
                }
            iter++;
            //System.out.printf("%d...\n", iter);
            for (int k = 0; k < numOfColors; ++k)
            {
                finish &= !clasters[k].recalculate(pixels, width);
                //System.out.printf("%x  ", clasters[k].getColor());
                clasters[k].lambda = lambda;
            }
            //System.out.print("\n");
        }
        do {
            finish = true;
            for (int j = 0; j < height; ++j)
                for (int i = 0; i < width; ++i) {
                    int idx = j * width + i;
                    int min = 0xffffff;
                    int minIdx = 0;
                    for (int k = 0; k < numOfColors; ++k)
                    {
                        int temp = clasters[k].getDistance(pixels[idx], i, j);
                        if (temp < min) {
                            min = temp;
                            minIdx = k;
                        }
                    }
                    clasters[minIdx].addPoint(i, j);
                }
            iter++;
            if (iter >= numOfIterations) {
                //System.out.println("iteration finish\n");
                break;
            }
            System.out.printf("%d...\n", iter);
            for (int k = 0; k < numOfColors; ++k)
            {
                finish &= !clasters[k].recalculate(pixels, width);
                //System.out.printf("%x  ", clasters[k].getColor());
            }
            //System.out.print("\n");
        } while (!finish);
        
        for (int k = 0; k < numOfColors; ++k)
            clasters[k].setNewColors(newPixels, width);

        myImage.setRGB(0, 0, width, height, newPixels, 0, width);

        updateHistory();
        fireImageChanged();
    }

    public void open(File file) {
        BufferedImage image = null;
        try {
            image = ImageIO.read(file);
        } catch (IOException e) {
            fireImageNotOpened(file.getAbsolutePath());
            return;
        }
        if (image != null) {
            myFileName = file.getName();
            myImage = image;
            myFile = file;
            dropHistory();
            updateHistory();
            fireImageChanged();
        } else {
            fireImageNotOpened(file.getAbsolutePath());
        }

    }

    public String getFileName() {
        return myFileName;
    }

    public File getFile() {
        return myFile;
    }

    public String getShortFileName() {
        int idx = myFileName.lastIndexOf(File.separator) + 1;
        return myFileName.substring(idx).toLowerCase();
    }

    public String getExtention() {
        String name = getShortFileName();
        int dot = name.lastIndexOf('.');
        return name.substring(dot + 1).toLowerCase();
    }
    
    public int compare()
    {
        String goldenPath = goldenImagePath + File.separator + getShortFileName();
        File goldenFile = new File(goldenPath);
        BufferedImage goldenImage = null;
        try {
           goldenImage = ImageIO.read(goldenFile);
        } catch (Exception ex)
        {
            return -1;
        }
        if (goldenImage != null && goldenImage.getWidth() == myImage.getWidth() && goldenImage.getHeight() == myImage.getHeight())
        {
            return compareWithImage(goldenImage);
        }
        else return -2;


    }
    
    private int compareWithImage(BufferedImage goldenImage) {
        double res = 0;
        double max = 0;
        int width = myImage.getWidth();
        int height = myImage.getHeight();
        int[] pixels = new int[width * height];
        int[] newPixels = new int[width * height];
        pixels = myImage.getRGB(0, 0, width, height, pixels, 0, width);
        newPixels = goldenImage.getRGB(0, 0, width, height, newPixels, 0, width);
        for (int i = 0; i < width * height; ++i) {
            int g = Math.abs((pixels[i] & 0xff) - (newPixels[i] & 0xff));
            int b = Math.abs(((pixels[i] & 0xff00) >> 8) - ((newPixels[i] & 0xff00) >> 8));
            int r = Math.abs(((pixels[i] & 0xff0000) >> 16) - ((newPixels[i] & 0xff0000) >> 16));
            newPixels[i] = (r << 16) + (b << 8) + g;
            res += r + b + g;
            max += 0xff * 3;
        }

        BufferedImage diffImage = new BufferedImage(width, height, myImage.getType());
        diffImage.setRGB(0, 0, width, height, newPixels, 0, width);
        File file = new File(diffPath + File.separator + getShortFileName());

        try {
                if (!ImageIO.write(diffImage, getExtention(), file)) {
                fireImageNotSaved(file.getName());
            }
        } catch (IOException e) {
            fireImageNotSaved(file.getName());
        }

        res = res * 100.0 / max;
        return (int)res;
    }

    public String getGoldenImagePath() {
        return  goldenImagePath;
    }

    public String getDiffPath() {
        return  diffPath;
    }

    public void setGoldenImagePath(String path) {
        goldenImagePath = path;
    }

    public void setDiffPath(String path) {
        diffPath = path;
    }


}
